
package com.kweative.model;


import com.kweative.model.access.TableAccessHandler;
import com.kweative.model.apt.annotation.TableDeclaration;
import com.kweative.model.data.Row;
import com.kweative.model.data.RowSet;
import com.kweative.model.exception.ColumnAlreadyExistsException;
import com.kweative.model.exception.ColumnNotFoundException;
import com.kweative.model.exception.IndexNotFoundException;
import com.kweative.model.exception.NotAddedToTableException;
import com.kweative.model.exception.StructureException;
import com.kweative.model.column.Column;
import com.kweative.model.column.ColumnExtra;
import com.kweative.model.column.ColumnInteger;
import com.kweative.model.column.IColumn;
import com.kweative.model.query.Delete;
import com.kweative.model.query.Insert;
import com.kweative.model.query.Select;
import com.kweative.model.query.Update;
import com.kweative.model.relation.Relation;
import com.kweative.model.relation.RelationOne2One;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author henrik
 */
@TableDeclaration
abstract public class Table<R extends Row<T>,T extends Table,S extends Schema> implements Cloneable {
    public ColumnInteger<T> __primary;
    protected String name = null;
    protected S schema;
    protected TableAccessHandler access;

    protected LinkedHashMap<String,IColumn> columns = new LinkedHashMap<String,IColumn>();
	protected HashMap<Class<Enum>,Enum> parameters = new HashMap<Class<Enum>, Enum>();
    protected LinkedList<Relation> relations  = new LinkedList<Relation>();
    protected HashMap<Table,Relation> parents  = new HashMap<Table, Relation>();
    protected ArrayList<Index> indexes = new ArrayList<Index>();
    protected HashMap<Integer,Row> rowcache = new HashMap<Integer, Row>();
    protected String alias;
    protected Index primary;

    
    public Table() {
        init(getClass().getSimpleName());
    }
    public Table(String name) {
        init(name);
    }
    public T as(String alias) {
        T c = null;
        
        c = (T) clone();
        c.alias = alias;
        return c;
    }
    public Class<R> getRowClass() {
        return (Class<R>) (new Row<T>((T)this)).getClass();
    }

    @Override
    public T clone() {
        try {
            T c = (T) super.clone();
            for(String fn : columns.keySet()) {
                Column f = ((Column)columns.get(fn)).clone();
                f.setTable(c);
                c.columns.put(fn,f);
                try {
                    c.getClass().getField(fn).set(c, f);
                } catch (Exception ex) {
                    //Not all columns are declared
                }
            }
            return c;
        } catch (CloneNotSupportedException ex) {
            Logger.getLogger(Table.class.getName()).log(Level.SEVERE, null, ex);
        }
        return null;
    }
	public void setParam(Class<Enum> type,Enum value) {
		parameters.put(type, value);
	}
	public Enum getParam(Class<Enum> type,Enum def) {
		if (parameters.containsKey(type))
			return parameters.get(type);
		return def;
	}


    /**
     * Initialize the global version of this table
     * @param name
     */
    protected void init(String name) {
        if (this.name != null) return;
        this.name = name;
        primary = new Index("PRIMARY", Index.Type.PRIMARY);
        addIndex(primary);
        try {
            __primary = addColumn(new ColumnInteger("__primary"), primary);
            __primary.setSQLExtra(ColumnExtra.AUTO_INCREMENT);

        } catch (IndexNotFoundException ex) {
            Logger.getLogger(Table.class.getName()).log(Level.SEVERE, null, ex);
        } catch (ColumnAlreadyExistsException ex) {
                Logger.getLogger(Table.class.getName()).log(Level.SEVERE, null, ex);
        }
		resolveFields();
    }
	protected void resolveFields() {
		for(Field f:getClass().getFields()) {
			try {
				Object fVal = f.get(this);
				if (fVal instanceof Column) {
					Column<?,T> col = (Column<?, T>) fVal;
					if (col.getName() == null || col.getName().isEmpty()) {
						col.setName(f.getName());
						columns.put(col.getName(),col);
					}
				} else if (fVal instanceof Index) {
					Index ix = (Index) fVal;
					if (ix.getName() == null || ix.getName().isEmpty()) {
						ix.setName(f.getName());
					}
				}
			} catch (IllegalArgumentException ex) {
				Logger.getLogger(Table.class.getName()).log(Level.SEVERE, null, ex);
			} catch (IllegalAccessException ex) {
				Logger.getLogger(Table.class.getName()).log(Level.SEVERE, null, ex);
			}
		}
	}
	protected void initRow(R row) {
		for(Field f:getClass().getFields()) {
			try {
				Object fVal = f.get(this);
				if (fVal instanceof Column) {
					Column<?,T> col = (Column<?, T>) fVal;
					if (col.getSQLDefault() == null) continue;

					Field rowField = row.getClass().getField(f.getName());
					rowField.set(row,col.getSQLDefault());
				}
			} catch (NoSuchFieldException ex) {
				Logger.getLogger(Table.class.getName()).log(Level.SEVERE, null, ex);
			} catch (SecurityException ex) {
				Logger.getLogger(Table.class.getName()).log(Level.SEVERE, null, ex);
			} catch (IllegalArgumentException ex) {
				Logger.getLogger(Table.class.getName()).log(Level.SEVERE, null, ex);
			} catch (IllegalAccessException ex) {
				Logger.getLogger(Table.class.getName()).log(Level.SEVERE, null, ex);
			}
		}
	}


    public S getSchema() {
        return schema;
    }

    public String getAlias() {
        return alias;
    }

    /* Simple getters */

    public String getName() {
        return name;
    }

    public Index getPrimaryIndex() {
        return primary;
    }
    public Column getPrimaryColumn() {
        return __primary;
    }
    public Column getColumn(String name) throws ColumnNotFoundException {
        if (!hasColumn(name))
            throw new ColumnNotFoundException(name, this);
        return (Column) columns.get(name.toString());

    }

	public LinkedHashMap<String, IColumn> getColumns() {
		return columns;
	}

	public ArrayList<Index> getIndexes() {
		return indexes;
	}

	public LinkedList<Relation> getRelations() {
		return relations;
	}

    public Connection getConnection() {
        return getSchema().getConnection();
    }
	
    /* Structure adding */

    public IColumn addColumn(IColumn col,Index index) throws IndexNotFoundException, ColumnAlreadyExistsException {
        if (!indexes.contains(index))
            throw new IndexNotFoundException(index, this);
        addColumn(col);

        try {
            index.addColumn(col);
        } catch (NotAddedToTableException ex) {
            Logger.getLogger(Table.class.getName()).log(Level.SEVERE, null, ex);
        } catch (ColumnNotFoundException ex) {
            Logger.getLogger(Table.class.getName()).log(Level.SEVERE, null, ex);
        }
        
        return col;
    }
    public IColumn addColumn(IColumn col) throws ColumnAlreadyExistsException {
		resolveFields();
        if (hasColumn(col))
            throw new ColumnAlreadyExistsException(col,this);
        col.setTable(this);
		if (!col.getName().isEmpty())
			columns.put(col.getName(),col);
        return col;
    }
	public <F extends Column> F addColumn(F col) throws ColumnAlreadyExistsException {
		addColumn((IColumn)col);
		return col;
    }
	public <F extends Column> F addColumn(F col,Index index) throws ColumnAlreadyExistsException, IndexNotFoundException {
		addColumn((IColumn)col,index);
		return col;
    }
    public boolean hasColumn(IColumn col) {
        return hasColumn(col.getName());
    }
    public boolean hasColumn(String colName) {
        return columns.containsKey(colName.toString());
    }
    public Index addIndex(Index index) {
		resolveFields();
        indexes.add(index);
        index.setTable(this);
        return index;
    }
    public Column getParentKey(Table parent) {
        if (parents.get(parent) instanceof RelationOne2One) {
            return ((RelationOne2One)parents.get(parent)).getChildColumn();
        }
        return null;
    }
    public Column getParentKey(Row parentRow) {
        Table parent = parentRow.getTable();
        return getParentKey(parent);
    }
    public <R extends Relation<?,T>> R addRelation(R relation) {
        relations.add(relation);
        relation.setChild((T)this);
        parents.put(relation.getParent(), relation);
        return relation;
    }

    public void setSchema(S schema) {
        this.schema = schema;
        this.schema.addTable(this);
        try {
            this.makeStructure();
        } catch (StructureException ex) {
            Logger.getLogger(Table.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    public R newRow() {
        try {
            R row = getRowClass().getConstructor(getClass()).newInstance(this);
			initRow(row);
			return row;
        } catch (Exception ex) {
            Logger.getLogger(Table.class.getName()).log(Level.SEVERE, null, ex);
        }
        return null;
    }

    /**
     * Method used to create the table structure (Called only when needed - usually only once)
     */
    abstract protected void makeStructure() throws StructureException;

    /* QUERY METHODS */
    public Select<T,R> select() {
        return new Select<T,R>((T)this);
    }
    public Insert<T> insert() {
        return new Insert<T>((T)this);

    }

    public int insert(R row) throws SQLException {
        return insert().from(row).execute();
    }
    public void update(int primary,Row row) throws SQLException {
        update(primary).from(row).execute();
    }
    public void update(R row) throws SQLException {
        update(row.__primary).from(row).execute();
    }
    public Update<T> update() {
        return new Update<T>((T)this);
    }
    public Update<T> update(int primary) {
        return update().row(primary);
    }
    public Delete<T> delete() {
        return new Delete<T>((T)this);
    }
    public Delete<T> delete(int primary) {
        return delete().row(primary);
    }
    /* STAMENTS */
    public Row<T> getRow(int primary) {
        return select().row(primary).first();
    }
    public RowSet getAll() throws SQLException {
        return select().all();
    }

    @Override
    public boolean equals(Object o) {
        if (!(o instanceof Table)) return false;
        Table tbl = (Table) o;
        return name.equals(tbl.getName()) && schema.equals(tbl.getSchema());
    }

    @Override
    public int hashCode() {
        int hash = 5;
        hash = 31 * hash + (this.name != null ? this.name.hashCode() : 0);
        hash = 31 * hash + (this.schema != null ? this.schema.hashCode() : 0);
        return hash;
    }
}
